﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SmartMathLibrary
{
    /// <summary>
    /// This class represents an convergence test for fix point problems like F(x, y, z) = f(x, y, z) + (x, y, z).
    /// </summary>
    [Serializable]
    public class RealVectorIterativeConvergenceTest : AbstractConvergenceTest
    {
        /// <summary>
        /// The function, which should be checked.
        /// </summary>
        private HardVectorFunctionPointer function;

        /// <summary>
        /// Initializes a new instance of the <see cref="RealVectorIterativeConvergenceTest"/> class.
        /// </summary>
        /// <param name="function">The function, which should be checked.</param>
        public RealVectorIterativeConvergenceTest(HardVectorFunctionPointer function)
        {
            this.function = function;
        }

        /// <summary>
        /// Gets or sets the function, which should be checked.
        /// </summary>
        /// <value>The function, which should be checked.</value>
        public HardVectorFunctionPointer Function
        {
            get { return function; }
            set { function = value; }
        }

        /// <summary>
        /// Checks the convergence of the specified fix point problem.
        /// </summary>
        /// <param name="startValue">The start values for the convergence check.</param>
        /// <param name="iterations">The maximn number of iterations.</param>
        /// <param name="precision">The precision of the result.</param>
        /// <returns>The fix point, which was find by the test.</returns>
        public GeneralVector CheckConvergence(GeneralVector startValue, int iterations, double precision)
        {
            GeneralVector oldValue = new GeneralVector(startValue.Count);

            for (int i = 0; i < oldValue.Count; i++)
            {
                oldValue[i] = Double.MinValue;
            }

            for (int i = 0; i < iterations; i++)
            {
                oldValue = startValue.Copy();
                startValue = this.function(startValue);

                if (CheckPrecision(startValue, oldValue, precision))
                {
                    this.convergence = true;
                    this.neededIterations = i + 1;
                    this.relativeError = Math.Abs(oldValue.Length - startValue.Length);

                    return startValue;
                }
            }

            this.convergence = false;
            this.neededIterations = iterations;
            this.relativeError = Math.Abs(oldValue.Length - startValue.Length);

            return startValue;
        }

        /// <summary>
        /// Checks the precision of two vectors.
        /// </summary>
        /// <param name="a">The first vector.</param>
        /// <param name="b">The second vector.</param>
        /// <param name="precision">The precision to check.</param>
        /// <returns>True, if all components of the vector (a - b) smaller than the specified precision.</returns>
        private static bool CheckPrecision(GeneralVector a, GeneralVector b, double precision)
        {
            for (int i = 0; i < a.Count; i++)
            {
                if (Math.Abs(a[i] - b[i]) > precision)
                {
                    return false;
                }
            }

            return true;
        }
    }
}